package com.zrc.ke.core;

import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.text.TextUtils;
import android.util.Log;

import com.zrc.ke.LogUtil;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;

/**
 * @Description:
 * @Author: wangzs
 * @Date: 2022-03-01 12:26
 * @Version:
 */
public class PluginManager {
    public final static String PLUGIN_NAME = "plugin-debug.apk";

    private String downLoadapk() {
        AssetManager am = context.getAssets();
        InputStream is = null;
        FileOutputStream fos = null;
        try {
            is = am.open(PLUGIN_NAME);
            File extractFile = context.getFileStreamPath(PLUGIN_NAME);
            fos = new FileOutputStream(extractFile);
            byte[] buffer = new byte[1024];
            int count = 0;
            while ((count = is.read(buffer)) > 0) {
                fos.write(buffer, 0, count);
            }
            fos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        File apkFile = context.getFileStreamPath(PLUGIN_NAME);
        return apkFile.getPath();
    }

    private static final String TAG = PluginManager.class.getName();

    private static PluginManager pluginManager;
    private ClassLoader dexClassLoader;
    private Resources resources;

    public PluginManager(Context context) {
        this.context = context;
    }

    private Context context;

    public static PluginManager getInstance(Context context) {
        if (pluginManager == null) {
            synchronized (PluginManager.class) {
                if (pluginManager == null) {
                    pluginManager = new PluginManager(context);
                }
            }
        }
        return pluginManager;
    }

    /**
     * 加载插件包的apk文件
     */
    public void loadPlugin() {
        String fileApkPath = downLoadapk();
        if (TextUtils.isEmpty(fileApkPath)) {
            return;
        }
        try {
            File apkFile = new File(fileApkPath);
            if (!apkFile.exists()) {
                Log.e(TAG, "loadPlugin: 插件不存在");
                return;
            }
            dexClassLoader=context.getClassLoader();
            resources = mergePluginResources(context, PLUGIN_NAME);
        } catch (Exception e) {
            Log.e(TAG, "loadPlugin: " + e.getMessage());
        }
    }

    /**
     * 合并插件中的资源
     *
     * @param context
     * @param apkName
     * @return 合并后新的资源对象
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws NoSuchFieldException
     */
    private static Resources mergePluginResources(Context context, String apkName) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        // 创建一个新的 AssetManager 对象
        AssetManager newAssetManagerObj = AssetManager.class.newInstance();
        Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
        // 塞入原来宿主的资源
        addAssetPath.invoke(newAssetManagerObj, context.getPackageResourcePath());
        // 塞入插件的资源
        File optDexFile = context.getFileStreamPath(apkName);
        addAssetPath.invoke(newAssetManagerObj, optDexFile.getAbsolutePath());

        // ----------------------------------------------

        // 创建一个新的 Resources 对象
        Resources newResourcesObj = new Resources(newAssetManagerObj, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());

        // ----------------------------------------------

        // 获取 ContextImpl 中的 Resources 类型的 mResources 变量，并替换它的值为新的 Resources 对象
        Field resourcesField = context.getClass().getDeclaredField("mResources");
        resourcesField.setAccessible(true);
        resourcesField.set(context, newResourcesObj);

        // ----------------------------------------------

        // 获取 ContextImpl 中的 LoadedApk 类型的 mPackageInfo 变量
        Field packageInfoField = context.getClass().getDeclaredField("mPackageInfo");
        packageInfoField.setAccessible(true);
        Object packageInfoObj = packageInfoField.get(context);

        // 获取 mPackageInfo 变量对象中类的 Resources 类型的 mResources 变量，，并替换它的值为新的 Resources 对象
        // 注意：这是最主要的需要替换的，如果不需要支持插件运行时更新，只留这一个就可以了
        Field resourcesField2 = packageInfoObj.getClass().getDeclaredField("mResources");
        resourcesField2.setAccessible(true);
        resourcesField2.set(packageInfoObj, newResourcesObj);

        // ----------------------------------------------

        // 获取 ContextImpl 中的 Resources.Theme 类型的 mTheme 变量，并至空它
        // 注意：清理mTheme对象，否则通过inflate方式加载资源会报错, 如果是activity动态加载插件，则需要把activity的mTheme对象也设置为null
        Field themeField = context.getClass().getDeclaredField("mTheme");
        themeField.setAccessible(true);
        themeField.set(context, null);

        return newResourcesObj;
    }

    public ClassLoader getDexClassLoader() {
        return dexClassLoader;
    }

    public Resources getResources() {
        return resources;
    }

    public void releasePlugin() {
        context = null;
        resources = null;
        dexClassLoader = null;
        pluginManager = null;
    }
}
